﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Windows.Forms;
using E4RPTools;
using Restaurant.Controls;
using RestaurantCommon;
using RestaurantExternal;

namespace Restaurant
{
    public class Controller
    {
        private SqlConnection _externalConnection;
        private SqlConnection _myConnection;


        #region Properties

        public IWin32Window OwnerWindow { get; set; }

        public PedidoDataSet Request { get; set; }

        #endregion

        #region Events

        public event EventHandler<EventArgs> RequestCloseEditors;

        public event EventHandler<EventArgs> RequestShow;

        public event EventHandler<EventArgs> RequestSaved;

        public event EventHandler<EventArgs> TableSaved;

        public event EventHandler<TableEventArgs> TableRequestChanged;

        public event EventHandler<EventArgs> ProductSynchronized;

        public event EventHandler<EventArgs> ProductTreeSaved;

        private void OnTableSaved()
        {
            EventHandler<EventArgs> tableSaved = TableSaved;
            if (tableSaved != null)
                tableSaved(this, new EventArgs());
        }

        private void OnRequestShow()
        {
            EventHandler<EventArgs> requestShow = RequestShow;
            if (requestShow != null)
                requestShow(this, new EventArgs());
        }

        private void OnRequestSaved()
        {
            EventHandler<EventArgs> requestSaved = RequestSaved;
            if (requestSaved != null)
                requestSaved(this, new EventArgs());
        }

        private void OnRequestCloseEditors()
        {
            EventHandler<EventArgs> requestCloseEditors = RequestCloseEditors;
            if (requestCloseEditors != null)
                requestCloseEditors(this, new EventArgs());
        }

        private void OnProductSynchronized()
        {
            EventHandler<EventArgs> productSynchronized = ProductSynchronized;
            if (productSynchronized != null)
                productSynchronized(this, new EventArgs());
        }

        private void OnProductTreeSaved()
        {
            EventHandler<EventArgs> productTreeSaved = ProductTreeSaved;
            if (productTreeSaved != null)
                productTreeSaved(this, new EventArgs());
        }

        private void OnTableRequestChanged(long tableId, long? requestId, DateTime initialTime, DateTime lastChangeTime, string stage, decimal value)
        {
            EventHandler<TableEventArgs> tableRequestChanged = TableRequestChanged;
            if (tableRequestChanged != null)
                tableRequestChanged(this, new TableEventArgs(tableId, requestId, initialTime, lastChangeTime, stage, value));
        }
        #endregion

        #region Private Methods

        private void PublishAndShowErrorMessage(Exception e, string errorMessage)
        {
            ExceptionPublisher.Publish(ConfigurationManager.AppSettings["LogFile"], e);
            ExceptionPublisher.ShowErrorMessage(OwnerWindow, errorMessage, "Restaurante");
        }

        private bool LoginMy(string userName, string password)
        {
            var dataSource = ConfigurationManager.AppSettings["FonteDados"];
            var databaseName = ConfigurationManager.AppSettings["NomeBaseDados"];
            var connectionString = "Data Source=" + dataSource + ";Initial Catalog=" + databaseName + ";User ID=" + userName + ";Password=" + password + ";MultipleActiveResultSets=True;";
            try
            {
                _myConnection = new SqlConnection(connectionString);
                _myConnection.Open();
            }
            catch (Exception e)
            {
                PublishAndShowErrorMessage(e, "Não foi possível ligar à base de dados.");
                return false;
            }
            return true;
        }

        private bool LoginExternal(string userName, string password)
        {
            var dataSource = ConfigurationManager.AppSettings["FonteDados"];
            var databaseName = ConfigurationManager.AppSettings["NomeBaseDadosExterna"];
            var connectionString = "Data Source=" + dataSource + ";Initial Catalog=" + databaseName + ";User ID=" + userName + ";Password=" + password + ";MultipleActiveResultSets=True;";
            try
            {
                _externalConnection = new SqlConnection(connectionString);
                _externalConnection.Open();
            }
            catch (Exception e)
            {
                PublishAndShowErrorMessage(e, "Não foi possível ligar à base de dados externa.");
                return false;
            }
            return true;
        }

        private void FixMyConnection()
        {
            if (_myConnection.State == ConnectionState.Open)
                _myConnection.Close();
            _myConnection.Open();
        }

        private PedidoDataSet GetRequest(long requestId)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                PedidoDataSet dataSet = RestaurantDb.GetRequest(sqlTransaction, requestId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler o pedido.");
                return new PedidoDataSet();
            }
        }

        private void FixExternalConnection()
        {
            if (_externalConnection.State == ConnectionState.Open)
                _externalConnection.Close();
            _externalConnection.Open();
        }

        private ProdutoDataSet GetExternalProducts()
        {
            FixExternalConnection();
            var sqlTransaction = _externalConnection.BeginTransaction();
            try
            {
                ProdutoDataSet dataSet = PHC.GetProducts(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de produtos externa.");
                return new ProdutoDataSet();
            }
        }

        private void SynchronizeProducts(ProdutoDataSet externalProducts)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                RestaurantDb.SynchronizeProducts(sqlTransaction, externalProducts);
                sqlTransaction.Commit();                
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível sincronizar os produtos.");                
            }
        }

        public ProdutoDataSet Product(string productReference)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                ProdutoDataSet dataSet = RestaurantDb.GetProduct(sqlTransaction, productReference);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler p produto '" + productReference + "'.");
                return new ProdutoDataSet();
            }
        }

        private PedidoDataSet.ItemPedidoRow GetRequestItem(string productReference)
        {
            foreach (var requestItem in Request.ItemPedido)
                if (requestItem.Referencia == productReference)
                    return requestItem;
            return null;
        }

        private long GetNextRequestItemId()
        {
            long lastId = 0;
            foreach(var requestItem in Request.ItemPedido)
            {
                var id = requestItem.RowState == DataRowState.Deleted
                              ? Convert.ToInt64(requestItem["id_item", DataRowVersion.Original])
                              : requestItem.IdItem;
                if (id < lastId)
                    lastId = id;
            }
            return lastId - 1;
        }

        private static void UpdateRequestTotals(PedidoDataSet dataSet)
        {
            dataSet.Pedido[0].Valor = 0;
            foreach (var requestItem in dataSet.ItemPedido)
            {
                if (requestItem.RowState == DataRowState.Deleted)
                    continue;
                dataSet.Pedido[0].Valor += requestItem.ValorTotal;
            }
        }

        private static void ClearErrors(DataTable table)
        {
            foreach (DataRow row in table.Rows)
                row.ClearErrors();
        }
        
        #endregion

        public bool Login(string userName, string password)
        {
            return LoginMy(userName, password) && LoginExternal(userName, password);
        }

        public void CloseConnections()
        {
            _myConnection.Close();
            _externalConnection.Close();
        }

        public MesaDataSet Tables()
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                MesaDataSet dataSet = RestaurantDb.GetTables(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de mesas.");
                return new MesaDataSet();
            }
        }

        public MesaDataSet GetTable(long tableId)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                MesaDataSet dataSet = RestaurantDb.GetTable(sqlTransaction, tableId);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a mesa.");
                return new MesaDataSet();
            }
        }

        public Result IsTableValid(MesaDataSet.MesaRow table)
        {
            var result = new Result();
            if (string.IsNullOrEmpty(table.Nome)) result.Add("Nome", "Por favor, preencha o nome.");
            if (table.IsNumeroLugaresNull()) result.Add("NumeroLugares", "Por favor, preencha o número de lugares.");
            if (!table.IsNumeroLugaresNull() && table.NumeroLugares < 0) result.Add("NumeroLugares", "Por favor, número de lugares de ser um valor positivo.");
            if (string.IsNullOrEmpty(table.CorFundo)) result.Add("CorFundo", "Por favor, preencha a cor do fundo.");
            if (string.IsNullOrEmpty(table.CorNome)) result.Add("CorNome", "Por favor, preencha a cor do nome.");
            return result;
        }

        public void SaveTable(MesaDataSet.MesaRow table)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                RestaurantDb.SaveTable(sqlTransaction, table);
                sqlTransaction.Commit();
                OnTableSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar a mesa.");
            }
        }

        public PedidoDataSet Requests()
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                PedidoDataSet dataSet = RestaurantDb.GetRequests(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de pedidos.");
                return new PedidoDataSet();
            }
        }

        public bool ExistsOpenedRequest(IWin32Window owner, string action)
        {
            if (Request == null)
                return true;
            OnRequestCloseEditors();
            if (Request.Pedido[0].Id < 0 && Request.ItemPedido.Count == 0)
                return true;
            if (!Request.HasChanges())
                return true;
            DialogResult result = MessageBox.Show(owner, "Existe um pedido por tratar." + Environment.NewLine + action,
                "Pedido", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);
            return result == DialogResult.Yes;
        }

        public void ShowRequest(long requestId)
        {
            Request = GetRequest(requestId);
            OnRequestShow();
        }

        public void NewRequest(long tableId, int numeroLugares)
        {
            Request = new PedidoDataSet();
            var request = Request.Pedido.NewPedidoRow();
            request.Id = -1;
            request.Numero = -1;
            request.Nome = "";
            request.DataInicio = DateTime.Now;
            request.Estado = RequestState.Novo.ToString();
            request.IdMesa = tableId;
            request.NumeroPessoas = numeroLugares;
            request.Valor = 0;
            request.ValorPago = 0;
            Request.Pedido.AddPedidoRow(request);            
            OnRequestShow();
        }

        public void NewRequest()
        {
            Request = new PedidoDataSet();
            var request = Request.Pedido.NewPedidoRow();
            request.Id = -1;
            request.Numero = -1;
            request.Nome = "";
            request.DataInicio = DateTime.Now;
            request.Estado = RequestState.Novo.ToString();            
            request.Valor = 0;
            request.ValorPago = 0;
            request.NumeroPessoas = 0;
            Request.Pedido.AddPedidoRow(request);
            OnRequestShow();
        }        

        public void SynchronizeProducts()
        {
            var externalProducts = GetExternalProducts();
            if (externalProducts.Produto.Count == 0)
                return;
            SynchronizeProducts(externalProducts);
            OnProductSynchronized();
        }

        public ProdutoDataSet Products()
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                ProdutoDataSet dataSet = RestaurantDb.GetProducts(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a lista de produtos.");
                return new ProdutoDataSet();
            }
        }

        public ArvoreProdutoDataSet ProductsTree()
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                ArvoreProdutoDataSet dataSet = RestaurantDb.GetProductTree(sqlTransaction);
                sqlTransaction.Commit();
                return dataSet;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível ler a àrvore de produtos.");
                return new ArvoreProdutoDataSet();
            }
        }

        public void UpdateProductTree(long id, string name)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                RestaurantDb.UpdateProductTree(sqlTransaction, id, name);
                sqlTransaction.Commit();
                OnProductTreeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível actualizar o nome do nó na àrvore de produtos.");                
            }
        }

        public void SaveProductTree(ArvoreProdutoDataSet dataSet)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                RestaurantDb.SaveProductTree(sqlTransaction, dataSet);
                sqlTransaction.Commit();
                OnProductTreeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível adicionar os produtos na àrvore de produtos.");
            }
        }

        public void DeleteProductTree(long id)
        {
            FixMyConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                RestaurantDb.DeleteProductTree(sqlTransaction, id);
                sqlTransaction.Commit();
                OnProductTreeSaved();
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível eliminar o nó na àrvore de produtos.");
            }
        }

        public void AddProduct(string productReference)
        {
            var requestItem = GetRequestItem(productReference);
            if (requestItem != null)
            {
                requestItem.Quantidade += 1;
                requestItem.ValorTotal = requestItem.Quantidade*requestItem.ValorUnitario;
            }
            else
            {
                ProdutoDataSet product = Product(productReference);
                requestItem = Request.ItemPedido.NewItemPedidoRow();
                requestItem.Id = Request.Pedido[0].Id;
                requestItem.IdItem = GetNextRequestItemId();
                requestItem.Referencia = product.Produto[0].Referencia;
                if(!product.Produto[0].IsDescricaoNull()) requestItem.Descricao = product.Produto[0].Descricao;
                requestItem.Quantidade = 1;
                requestItem.ValorUnitario = product.Produto[0].Valor;
                requestItem.PercentagemIva = product.Produto[0].PercentagemIva;
                requestItem.ValorTotal = product.Produto[0].Valor;
                requestItem.Stock = product.Produto[0].Stock;
                if (!product.Produto[0].IsCategoriaNull()) requestItem.Categoria = product.Produto[0].Categoria;
                Request.ItemPedido.AddItemPedidoRow(requestItem);
            }
        }

        public void DeleteRequestItem(string productReference)
        {
            GetRequestItem(productReference).Delete();            
        }

        public Result IsRequestValid()
        {
            var result = new Result();
            var estimate = Request.Pedido[0];

            if (estimate.IsIdMesaNull() || estimate.IdMesa < 0) result.Add("IdMesa", "Por favor, preencha a mesa.");
            if (estimate.IsDataInicioNull() || estimate.DataInicio == DateTime.MinValue) result.Add("DataInicio", "Por favor, preencha a data de início.");
            ClearErrors(Request.ItemPedido);
            foreach (var estimateItem in Request.ItemPedido)
            {
                if (estimateItem.RowState == DataRowState.Deleted)
                    continue;
                if (estimateItem.IsReferenciaNull() || estimateItem.Referencia == "")
                    estimateItem.SetColumnError("Referencia", "Por favor, preencha o produto.");
                if (estimateItem.IsQuantidadeNull() || estimateItem.Quantidade < 0)
                    estimateItem.SetColumnError("Quantidade", "Por favor, a quantidade não pode ser negativo.");
                if (estimateItem.IsValorUnitarioNull() || estimateItem.ValorUnitario < 0)
                    estimateItem.SetColumnError("ValorUnitario", "Por favor, o valor unitário não pode ser negativo.");
                if (estimateItem.IsPercentagemIvaNull() || estimateItem.PercentagemIva < 0)
                    estimateItem.SetColumnError("PercentagemIva", "Por favor, a percentagem de iva não pode ser negativa.");
                if (estimateItem.IsValorTotalNull() || estimateItem.ValorTotal < 0)
                    estimateItem.SetColumnError("ValorTotal", "Por favor, o valor total não pode ser negativo.");
            }
            return result;
        }

        public bool SaveRequest(IWin32Window owner)
        {
            var sqlTransaction = _myConnection.BeginTransaction();
            try
            {
                UpdateRequestTotals(Request);
                Request.Pedido[0].DataActualizacao = DateTime.Now;
                RestaurantDb.SaveRequest(sqlTransaction, Request.Pedido[0]);
                RestaurantDb.UpdateRequestItems(sqlTransaction, Request.ItemPedido);
                sqlTransaction.Commit();
                OnRequestSaved();
                OnTableRequestChanged(Request.Pedido[0].IdMesa, Request.Pedido[0].Id, Request.Pedido[0].DataInicio, Request.Pedido[0].DataActualizacao, "ADefinir", 
                    Request.Pedido[0].Valor);
                NewRequest();
                return true;
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível gravar o pedido.");
                return false;
            }
        }

       
        private PhcSale BuildPhcSale()
        {
            var phcSaleItems = new List<PhcSaleItem>();
            foreach (var saleItem in Request.ItemPedido)
            {
                var phcSaleItem = new PhcSaleItem(saleItem.Referencia, "ProductName", "FamilyName",
                    saleItem.Quantidade, saleItem.PercentagemIva, Convert.ToInt16(saleItem.Stock), saleItem.ValorUnitario);
                phcSaleItems.Add(phcSaleItem);
            }
            var phcSale = new PhcSale("", null, "", Request.Pedido[0].DataFecho, "ClientName", "FiscalNumber", 0, "ClientCity", "ClientStreet", "ClientZipCode",
                                      "ClientZone", "ClientPhone", "ClientMail", "ClientRemarks", "PaymentType", Request.Pedido[0].Valor, Request.Pedido[0].ValorPago, phcSaleItems);
            return phcSale;
        }
        

        //private ExternalSale SaveExternalSale(Sale sale)
        //{
        //    var phcSaleItems = new List<PHC.SaleItem>();
        //    foreach (SaleItem saleItem in sale.SaleItem)
        //    {
        //        var phcSaleItem = new PHC.SaleItem(saleItem.Reference, saleItem.ProductName, saleItem.FamilyName,
        //            Convert.ToDecimal(saleItem.Quantity), saleItem.VatPercentage, Convert.ToInt16(saleItem.IsService), saleItem.Value);
        //        phcSaleItems.Add(phcSaleItem);
        //    }
        //    var phcSale = new PHC.Sale(sale.ExternalSaleId, sale.ExternalSaleNumber, _sessionId, sale.ExternalUserName,
        //        sale.Emission, sale.ClientName, sale.ClientFiscalNumber, Convert.ToInt64(sale.ClientNumber), sale.ClientCity, sale.ClientStreet,
        //        sale.ClientZipCode, sale.ClientZone, sale.ClientPhone, sale.ClientMail, sale.ClientRemarks, sale.PaymentType, sale.Value,
        //        sale.ValuePaid.Value, phcSaleItems);
        //    var result = PHC.PHC.SaveSale(_externalConnection, phcSale);
        //    return new ExternalSale(result.ExternalSaleId, result.ExternalSaleNumber, result.ExternalUserName);
        //}

        public bool BillRequest(IWin32Window owner)
        {
            FixExternalConnection();
            var sqlTransaction = _myConnection.BeginTransaction();
            
            try
            {

                var result = PHC.SaveSale(sqlTransaction, BuildPhcSale());
                if(!result.OK)
                {
                    MessageBox.Show(owner, result.ErrorMessage, "Facturar Pedido", MessageBoxButtons.OK, MessageBoxIcon.Information);
                    return false;
                }
            }
            catch (Exception e)
            {
                sqlTransaction.Rollback();
                PublishAndShowErrorMessage(e, "Não foi possível facturar o pedido.");
                return false;
            }
            return true;
        }
        
    }
}
